package com.ptl.bp.service;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ptl.bp.config.ApiResult;
import com.ptl.bp.config.BizException;
import com.ptl.bp.entity.TokenInfoEntity;
import com.ptl.bp.entity.UserEntity;
import com.ptl.bp.dto.TokenData;
import com.ptl.bp.dto.TokenRefreshRequest;
import com.ptl.bp.mapper.TokenInfoMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class AuthService extends ServiceImpl<TokenInfoMapper, TokenInfoEntity> {

    @Resource
    private UserService userService;

    @Value("${app.jwt.secret}")
    private String jwtSecret;

    @Value("${app.jwt.expiration-ms}")
    private int jwtExpirationMs;

    @Value("${app.jwt.refresh-expiration-ms}")
    private int refreshExpirationMs;

    public TokenInfoEntity login(UserEntity entity) {
        UserEntity old = userService.lambdaQuery().eq(UserEntity::getUsername, entity.getUsername())
                .oneOpt()
                .orElseThrow(() -> new BizException(ApiResult.ResStatusEnum.BAD_REQUEST, "账号密码错误"));

        if (!Objects.equals(old.getPassword(), entity.getPassword())) {
            throw new BizException("账号密码错误");
        }

        String accessToken = generateToken(old.getId(), jwtExpirationMs);
        String refreshToken = generateToken(old.getId(), refreshExpirationMs);

        // Calculate expiry dates
        Date accessTokenExpiry = new Date(System.currentTimeMillis() + jwtExpirationMs);
        Date refreshTokenExpiry = new Date(System.currentTimeMillis() + refreshExpirationMs);

        // Save or update token info
        Optional<TokenInfoEntity> existingToken = this.lambdaQuery().eq(TokenInfoEntity::getId, old.getId()).oneOpt();
        TokenInfoEntity tokenInfoEntity = existingToken.orElseGet(TokenInfoEntity::new);
        tokenInfoEntity.setAccessToken(accessToken);
        tokenInfoEntity.setRefreshToken(refreshToken);
        tokenInfoEntity.setAccessTokenExpiry(accessTokenExpiry);
        tokenInfoEntity.setRefreshTokenExpiry(refreshTokenExpiry);
        this.saveOrUpdate(tokenInfoEntity);
        return tokenInfoEntity;
    }

    @Transactional
    public TokenInfoEntity refreshToken(TokenRefreshRequest refreshRequest) {
        TokenInfoEntity tokenInfoEntity = this.lambdaQuery()
                .eq(TokenInfoEntity::getRefreshToken, refreshRequest.getRefreshToken())
                .oneOpt()
                .orElseThrow(() -> new BizException(ApiResult.ResStatusEnum.UNAUTHORIZED, "RefreshToken不存在"));

        if (!validateToken(tokenInfoEntity.getRefreshToken())) {
            throw new BizException(ApiResult.ResStatusEnum.UNAUTHORIZED, "RefreshToken已经过期");
        }

        // Generate new access token
        String newAccessToken = generateToken(tokenInfoEntity.getId(), refreshExpirationMs);
        Date newAccessTokenExpiry = new Date(System.currentTimeMillis() + jwtExpirationMs);

        tokenInfoEntity.setAccessToken(newAccessToken);
        tokenInfoEntity.setAccessTokenExpiry(newAccessTokenExpiry);

        this.saveOrUpdate(tokenInfoEntity);
        return tokenInfoEntity;
    }

    private String generateToken(Long id, int expiration) {
        TokenData value = TokenData.builder().id(id).build();
        return Jwts.builder()
                .setClaims(JSON.parseObject(JSON.toJSONString(value)))
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public boolean validateToken(String token) {
        Date expiration = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();
        return expiration.after(new Date());
    }

    public TokenData getTokenData(String token) {
        Claims claims = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody();
        return JSON.parseObject(JSON.toJSONString(claims), TokenData.class);
    }
}